{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# 第26章序列到序列模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 习题26.1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "&emsp;&emsp;设计由4层LSTM组成的序列到序列的基本模型，写出其公式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答思路：**\n",
    "\n",
    "1. 给出序列到序列的基本模型\n",
    "2. 设计基于4层LSTM的编码器和解码器，绘制模型架构图\n",
    "3. 结合模型架构图，写出模型公式\n",
    "4. 自编程实现模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答步骤：**  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第1步：序列到序列的基本模型**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据书中第26章的本章概要的序列到序列的基本模型：\n",
    "\n",
    "> &emsp;&emsp;对于序列到序列基本模型，编码器和解码器是循环神经网络，通常是LSTM和GRU。  \n",
    "> 编码器的状态是\n",
    "> $$ h_j = a(x_j, h_{j - 1}), \\ j = 1, 2, \\cdots, m $$\n",
    "> 解码器的状态是\n",
    "> $$ s_i = a(y_{i - 1}, s_{i - 1}), \\ i = 1, 2, \\cdots, n $$\n",
    "> 解码器的输出是\n",
    "> $$ p_i = g(s_i), \\ i = 1, 2, \\cdots, n $$\n",
    "> 编码器的最终状态$h_m$是解码器的初始状态$s_0$。\n",
    "> $$ s_0 = h_m $$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第2步：设计基于4层LSTM的编码器和解码器，绘制模型架构图**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![习题26.1解答图S2S-LSTM.png](../images/26-1-S2S-LSTM.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第3步：结合模型架构图，写出模型公式**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;对于编码器部分，输入为 $W_E$，经过嵌入层编码得 $X_E$，再经过4层 LSTM 编码，可得\n",
    "\n",
    "$$\n",
    "X_E = \\text{Embedding} (W_E) \\\\\n",
    "H_E^1 = \\text{Encoder LSTM1} (X_E) \\\\\n",
    "H_E^2 = \\text{Encoder LSTM2} (H_E^1) \\\\\n",
    "H_E^3 = \\text{Encoder LSTM3} (H_E^2) \\\\\n",
    "H_E^4 = \\text{Encoder LSTM4} (H_E^3)\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;取最后一层的输出 $\\boldsymbol H_E^4$ 中最终状态 $\\boldsymbol h_E^4$，通过广播机制扩展至与解码器输入序列相同维度，进行拼接\n",
    "\n",
    "$$\n",
    "X_D = \\text{Embedding} (W_D) \\\\\n",
    "H_D^1 = \\text{Decoder LSTM1} ([X_D, h_E^4.\\text{prob()}]) \\\\\n",
    "H_D^2 = \\text{Decoder LSTM2} (H_D^1) \\\\\n",
    "H_D^3 = \\text{Decoder LSTM3} (H_D^2) \\\\\n",
    "H_D^4 = \\text{Decoder LSTM4} (H_D^3) \\\\\n",
    "Y = \\text{Output}(H_D^4)\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第4步：自编程实现模型**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class S2SEncoder(nn.Module):\n",
    "    r\"\"\"Info: 由LSTM组成的序列到序列编码器。\n",
    "\n",
    "    Args:\n",
    "        inp_size: 嵌入层的输入维度。\n",
    "        embed_size: 嵌入层的输出维度。\n",
    "        num_hids: LSTM 隐层向量维度。\n",
    "        num_layers: LSTM 层数，本题目设置为4。\n",
    "    \"\"\"\n",
    "    def __init__(self, inp_size, embed_size, num_hids,\n",
    "                num_layers, dropout=0, **kwargs):\n",
    "        super(S2SEncoder, self).__init__(**kwargs)\n",
    "        \n",
    "        self.embed = nn.Embedding(inp_size, embed_size)\n",
    "        self.rnn = nn.LSTM(embed_size, num_hids, num_layers,\n",
    "                          dropout=dropout)\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        # inputs.shape(): (seq_length, embed_size)\n",
    "        inputs = self.embed(inputs)\n",
    "\n",
    "        # output.shape(): (seq_length, num_hids)\n",
    "        # states.shape(): (num_layers, num_hids)\n",
    "        output, state = self.rnn(inputs)\n",
    "\n",
    "        return output, state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class S2SDecoder(nn.Module):\n",
    "    r\"\"\"Info: 由LSTM组成的序列到序列解码器。\n",
    "\n",
    "    Args:\n",
    "        inp_size: 嵌入层的输入维度。\n",
    "        embed_size: 嵌入层的输出维度。\n",
    "        num_hids: LSTM 隐层向量维度。\n",
    "        num_layers: LSTM 层数，本题目设置为4。\n",
    "    \"\"\"\n",
    "    def __init__(self, inp_size, embed_size, num_hids,\n",
    "                num_layers, dropout=0, **kwargs):\n",
    "        super(S2SDecoder, self).__init__(**kwargs)\n",
    "        self.num_layers = num_layers\n",
    "        self.embed = nn.Embedding(inp_size, embed_size)\n",
    "        # 解码器 LSTM 的输入，由目标序列的嵌入向量和编码器的隐层向量拼接而成。\n",
    "        self.rnn = nn.LSTM(embed_size + num_hids, num_hids, num_layers,\n",
    "                          dropout=dropout)\n",
    "\n",
    "        self.linear = nn.Linear(num_hids, inp_size)\n",
    "\n",
    "    def init_state(self, enc_outputs, *args):\n",
    "        return enc_outputs[1][-1]\n",
    "\n",
    "    def forward(self, inputs, state):\n",
    "        # inputs.shape(): (seq_length, embed_size)\n",
    "        inputs = self.embed(inputs)\n",
    "\n",
    "        # 广播编码器最后输出的隐藏状态，使其具有与 inputs 相同的长度\n",
    "        # context.shape(): (seq_length, hid_size)\n",
    "        context = state[-1].repeat(inputs.shape[0], 1)\n",
    "        inputs = torch.cat((inputs, context), 1)\n",
    "        # output.shape(): (seq_length, num_hids)\n",
    "        output, _ = self.rnn(inputs)\n",
    "\n",
    "        output = self.linear(output)\n",
    "\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class EncoderDecoder(nn.Module):\n",
    "    r\"\"\"Info: 基于 LSTM 的序列到序列模型。\n",
    "\n",
    "    Args:\n",
    "        encoder: 编码器。\n",
    "        decoder: 解码器。\n",
    "    \"\"\"\n",
    "    def __init__(self, encoder, decoder, **kwargs):\n",
    "        super(EncoderDecoder, self).__init__(**kwargs)\n",
    "        self.encoder = encoder\n",
    "        self.decoder = decoder\n",
    "\n",
    "    def forward(self, enc_inp, dec_inp):\n",
    "        enc_out = self.encoder(enc_inp)\n",
    "        dec_state = self.decoder.init_state(enc_out)\n",
    "\n",
    "        return self.decoder(dec_inp, dec_state)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 搭建一个4层LSTM构成的序列到序列模型，进行前向计算\n",
    "inp_size, embed_size, num_hids, num_layers = 4, 8, 16, 4\n",
    "encoder = S2SEncoder(inp_size, embed_size, num_hids, num_layers)\n",
    "decoder = S2SDecoder(inp_size, embed_size, num_hids, num_layers)\n",
    "model = EncoderDecoder(encoder, decoder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.1821,  0.2403, -0.0596,  0.1208],\n",
      "        [-0.1576,  0.2332, -0.0129,  0.1142],\n",
      "        [-0.1435,  0.2280,  0.0110,  0.1143],\n",
      "        [-0.1357,  0.2243,  0.0222,  0.1161]], grad_fn=<AddmmBackward0>)\n"
     ]
    }
   ],
   "source": [
    "enc_inp_seq = \"I love you !\"\n",
    "dec_inp_seq = \"我 爱 你 ！\"\n",
    "enc_inp, dec_inp = [], []\n",
    "\n",
    "# 自己构造的的词典\n",
    "voc_1 = {\"I\": 1, \n",
    "        \"love\": 2,\n",
    "        \"you\": 3,\n",
    "        \"!\": 0}\n",
    "voc_2 = {\"我\": 1,\n",
    "        \"爱\": 2,\n",
    "        \"你\": 3,\n",
    "        \"！\": 0}\n",
    "\n",
    "for word in enc_inp_seq.split():\n",
    "    enc_inp.append(voc_1[word])\n",
    "\n",
    "enc_inp = torch.tensor(enc_inp)\n",
    "\n",
    "for word in dec_inp_seq.split():\n",
    "    dec_inp.append(voc_2[word])\n",
    "    \n",
    "dec_inp = torch.tensor(dec_inp)\n",
    "output = model(enc_inp, dec_inp)\n",
    "\n",
    "# output (dec_seq_len, dec_voc_size)\n",
    "print(output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 习题26.2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;比较基本模型和RNN Search的异同。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答思路：**\n",
    "\n",
    "1. 给出基本模型\n",
    "2. 给出RNN Search模型\n",
    "3. 比较基本模型和RNN Search的异同"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答步骤：**   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第1步：基本模型**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据书中第26章的本章概要的序列到序列的基本模型：\n",
    "\n",
    "> 对于序列到序列基本模型，编码器和解码器是循环神经网络，通常是LSTM和GRU。  \n",
    "> 编码器的状态是\n",
    "> $$ h_j = a(x_j, h_{j - 1}), \\ j = 1, 2, \\cdots, m $$\n",
    "> 解码器的状态是\n",
    "> $$ s_i = a(y_{i - 1}, s_{i - 1}), \\ i = 1, 2, \\cdots, n $$\n",
    "> 解码器的输出是\n",
    "> $$ p_i = g(s_i), \\ i = 1, 2, \\cdots, n $$\n",
    "> 编码器的最终状态$h_m$是解码器的初始状态$s_0$。\n",
    "> $$ s_0 = h_m $$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第2步：RNN Search模型**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据书中第26章的本章概要的RNN Search模型：  \n",
    "\n",
    "> &emsp;&emsp;RNN Search模型用双向LSTM实现编码，用单向LSTM实现解码，用注意力实现编码器到解码器的信息传递。在输出单词序列的每一个位置，通过注意力搜索到输入单词序列中的相关内容，以影响下一个位置的单词生成。  \n",
    "> &emsp;&emsp;编码器的状态是\n",
    "> $$ h_j^{(1)} = a \\left( x_j, h_{j - 1}^{(1)} \\right), \\ j = 1, 2, \\cdots, m \\\\\n",
    "h_j^{(2)} = a \\left( x_j, h_{j + 1}^{(2)} \\right), \\ j = m, m - 1, \\cdots, 1 \\\\\n",
    "h_j = [h_j^{(1)};h_j^{(2)}], \\ j = 1, 2, \\cdots, m $$\n",
    "> 解码器的状态是\n",
    "> $$ s_i = a (y_{i - 1}, s_{i - 1}, c_i), \\ i = 1, 2, \\cdots, n $$\n",
    "> 解码器的输出是\n",
    "> $$ p_i = g(s_i), \\ i = 1, 2, \\cdots, n $$\n",
    "> 通过注意力计算上下文向量$c_i$。注意力的查询是前一个位置的状态$s_{i - 1}$，键和值是编码器的各个位置上的中间表示$h_j$。\n",
    "> $$ c_i = \\sum_{j = 1}^m \\alpha_{ij} h_j, \\ i = 1, 2, \\cdots, n \\\\\n",
    "\\alpha_{ij} = \\frac{\\exp(e_{ij})}{\\sum_{k - 1}^m \\exp (e_{ik})}, \\ i = 1, 2, \\cdots, n, \\ j = 1, 2, \\cdots, m \\\\\n",
    "e_{ij} = \\sigma(w^T \\cdot [s_{i - 1}; h_j] + b), \\ i = 1, 2, \\cdots, n, \\ j = 1, 2, \\cdots, m $$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第3步：比较基本模型和RNN Search的异同**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 相同点：\n",
    "    1. 两者都由编码器和解码器组成，编码器和解码器都是循环神经网络\n",
    "    2. 两者都能解决序列到序列的学习任务，属于序列到序列模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 不同点：\n",
    "    1. 编码器：RNN Search的编码器使用双向LSTM，将各个位置对正向和反向状态进行拼接，得到中间表示；基本模型仅使用单向RNN。\n",
    "    2. 解码器：RNN Search使用单向LSTM，在解码器的每一个位置，通过加法注意力计算上下文向量，在解码的过程中，将编码器得到的状态序列或中间表示序列通过注意力有选择地传递到解码器，决定解码器的状态序列，以及输出的单词序列。基本模型使用单向RNN，编码器将其最终状态作为整个输入单词序列的表示传递给解码器。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 习题26.3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;写出多头自注意力的对损失函数的求导公式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答思路：**  \n",
    "\n",
    "1. 给出多头注意力的计算公式\n",
    "2. 给出多头自注意力的计算公式\n",
    "3. 写出多头自注意力的对损失函数的求导公式"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答步骤：**   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第1步：多头注意力的计算公式**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据书中第26.3.1的多头注意力的描述：\n",
    "\n",
    "> &emsp;&emsp;多头是指多个并行的注意力。在多头注意力中，先通过线性变换将表示向量从所在的空间分别投影到多个不同的子空间，每个子空间对应一个头，接着在各个子空间分别进行注意力计算，之后将各个子空间的注意力计算结果进行拼接，最后再对拼接结果进行线性变换，得到的表示向量的维度与原来的表示向量的维度相同。  \n",
    "> &emsp;&emsp;设$Q$是查询矩阵，$K$是键矩阵，$V$是值矩阵。多头注意力multi_attend的计算是\n",
    "> $$ \\text{multi\\_attend}(Q,K,V) = W_o \\cdot \\text{concate}(U_1, U_2, \\cdots, U_h) \\\\\n",
    "U_i = \\text{attend} \\left( W_Q^{(i)}Q, W_K^{(i)}K, W_V^{(i)}V \\right), \\ i = 1, 2, \\cdots, h $$\n",
    "> 其中，$h$是头的个数，$U_i$是第$i$个头的注意力计算结果，concate是矩阵向量的拼接，$W_O$是线性变换矩阵。$W_Q^{(i)}, W_K^{(i)}, W_V^{(i)}$分别是第$i$个头的查询矩阵、键矩阵、值矩阵的线性变换矩阵，attend是注意力函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第2步：多头自注意力的计算公式**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据书中第26.3.1的多头自注意力的描述：\n",
    "> &emsp;&emsp;当注意力中的查询、键、值向量$Q,K,V$相同，或者说自己时，称为自注意力。多头自注意力是有多个头的自注意力。  \n",
    "> &emsp;&emsp;在解码器中，多头自注意力计算对之后的位置进行掩码处理，让这些位置不参与计算。具体导入矩阵$M$，自注意力计算变成以下的掩码自注意力计算：\n",
    "> $$ \\text{attend} (Q, K, V) = V \\cdot \\text{softmax} \\left( \\frac{K^T \\cdot Q + M}{\\sqrt{d_k}} \\right) \\\\\n",
    "M = [m_{ij}], m_{ij} = \\left \\{  \n",
    "\\begin{array}{ll}\n",
    "0, & i \\leqslant j \\\\\n",
    "-\\infty, & \\text{其他}\n",
    "\\end{array}\n",
    "\\right. $$\n",
    "> 也就是说，自注意力在每一个位置以该位置的表示向量作为查询向量，该位置和之前位置的所有表示向量作为键向量和值向量。掩码注意力保证了解码的过程是自回归的，学习时可以使用强制教学的方法，即训练在各个位置上并行进行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第3步：写出多头自注意力的对损失函数的求导公式**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;假设有某个 $N$ 层的 Transformer 模型，损失函数为$L$，第 $l$ 层的输入为 $X^{(l-1)}$，其中第 $i$ 个的头注意力层输出为 $Z_i$，可得：\n",
    "\n",
    "$$\n",
    "Q_i^{(l)} = W^{(l)}_{i,q} X^{(l-1)} \\\\\n",
    "K_i^{(l)} = W^{(l)}_{i,k} X^{(l-1)} \\\\ \n",
    "V_i^{(l)} = W^{(l)}_{i,v} X^{(l-1)} \\\\\n",
    "Z_i^{(l)} = \\text{attend} (Q^{(l)}_i, K^{(l)}_i, V^{(l)}_i) = V_i^{(l)} \\cdot \\text{softmax} \\left(\\frac{K^{(l)}_i \\cdot Q^{(l)}_i }{\\sqrt{d_k}} \\right) = V^{(l)}_i \\cdot \\text{softmax}(H^{(l)}_i) \n",
    "$$\n",
    "其中\n",
    "$$\n",
    "H_i^{(l)} = \\frac{K^{(l)}_i \\cdot Q^{(l)}_i }{\\sqrt{d_k}}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据softmax函数$a_i = \\text{softmax}(z_i)$的求导公式：\n",
    "$$\n",
    "\\frac{\\partial a_i}{\\partial z_i} = a_i(1-a_i)\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据链式求导法则，损失函数$L$分别对$W^{(l)}_{i,q}, W^{(l)}_{i,k}, W^{(l)}_{i,v}$ 进行求导："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{aligned}\n",
    "\\frac{\\partial L}{\\partial W_{i,q}^{(l)}} \n",
    "&= \\frac{\\partial L}{\\partial Z_i^{(l)}} \\frac{\\partial Z_i^{(l)}}{\\partial W_{i,q}^{(l)}} \\\\\n",
    "&= \\frac{\\partial L}{\\partial Z_i^{(l)}} \\frac{\\partial (Z_i^{(l)})}{\\partial \\text{softmax}(H^{(l)}_i)} \\frac{\\partial \\text{softmax}(H^{(l)}_i)}{\\partial H_i^{(l)}} \\frac{\\partial H^{(l)}_i}{\\partial W_{i,q}^{(l)}} \\\\\n",
    "&= \\frac{\\partial L}{\\partial Z_i^{(l)}} Z_i^l \\left ( 1-\\frac{Z_i^l}{W^l_{i,v} {X^{l-1}} } \\right ) \n",
    "\\left (\\frac{{X^{l-1}} \\cdot W^l_{i,k} {X^{l-1}}}{\\sqrt{d_k}} \\right)\n",
    "\\end{aligned}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{aligned}\n",
    "\\frac{\\partial L}{\\partial W_{i,k}^{(l)}} \n",
    "&= \\frac{\\partial L}{\\partial Z_{i}^{(l)}} \\frac{\\partial Z_{i}^{(l)}}{\\partial W_{i,k}^{(l)}} \\\\\n",
    "&= \\frac{\\partial L}{\\partial Z_{i}^{(l)}} \\frac{\\partial (Z_{i}^{(l)})}{\\partial \\text{softmax}(H^{(l)}_{i})} \\frac{\\partial \\text{softmax}(H^{(l)}_{i})}{\\partial H_{i}^{(l)}} \\frac{\\partial H^{(l)}_{i}}{\\partial W_{i,k}^{(l)}} \\\\\n",
    "&= \\frac{\\partial L}{\\partial Z_{i}^{(l)}} Z_{i}^{(l)} \\left( 1 - \\frac{Z_{i}^{(l)}}{W^{(l)}_{i,v} {X^{l-1}} } \\right) \\left( \\frac{{X^{(l-1)}} \\cdot W^{(l)}_{i,q} {X^{(l-1)}}}{\\sqrt{d_k}} \\right) \n",
    "\\end{aligned}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{aligned}\n",
    "\\frac{\\partial L}{\\partial W_{i,v}^{(l)}} \n",
    "&= \\frac{\\partial L}{\\partial Z_i^{(l)}} \\frac{\\partial Z_{i}^{(l)}}{\\partial W_{i,v}^{(l)}} \\\\\n",
    "&= \\frac{\\partial L}{\\partial Z_i^{(l)}} \\frac{\\partial \n",
    "\\left ( \\text{softmax} \\left( \\frac{Q^{(l)}_i \\cdot K^{(l)}_i}{\\sqrt{d_k}} \\right) \\cdot V^{(l)}_i \\right)}{\\partial W_{i,v}^{(l)}} \\\\\n",
    "&= \\frac{\\partial L}{\\partial Z_i^{(l)}} \\text{softmax} \\left( \\frac{W^{(l)}_{i,q} {X^{(l-1)}} \\cdot W^{(l)}_{i,k} {X^{(l-1)}}}{\\sqrt{d_k}} \\right) \\cdot {X^{(l-1)}}\n",
    "\\end{aligned}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 习题26.4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;设计一个基于CNN的序列到序列模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答思路：**  \n",
    "\n",
    "1. 给出基于CNN的序列到序列模型\n",
    "2. 根据模型架构图，分析编码器和解码器的构成\n",
    "3. 自编程实现基于CNN的序列到序列模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答步骤：**   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第1步：基于CNN的序列到序列模型**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据论文[《Convolutional Sequence to Sequence Learning》](https://arxiv.org/abs/1705.03122)中的基于CNN的序列到序列模型的架构图\n",
    "\n",
    "![习题26.4解答图CNN.png](../images/26-4-CNN.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第2步：根据模型架构图，分析编码器和解码器的构成**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以机器翻译为任务为例，对模型网络进行分析：\n",
    "\n",
    "1. 在最上面的编码器部分，首先对输入文本进行 Embedding 计算，通过层叠的卷积抽取输入源文本序列的特征，卷积之后经过激活函数作为编码器的输出\n",
    "2. 在左下的解码器部分，用层叠卷积抽取输出目标序列的特征，经过GLU激活函数作为解码器的输出\n",
    "3. 在中间的注意力层，将编码器和解码器的输出进行点乘，作为输入的源语言序列中每个词权重\n",
    "4. 最后在残差连接部分，把注意力计算的权重与输入序列相乘，加入到解码器的输出中得到输出序列"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第3步：自编程实现基于CNN的序列到序列模型**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CNNEncoder(nn.Module):\n",
    "    r\"\"\"Info: 序列到序列 CNN 编码器。\n",
    "\n",
    "    Args:\n",
    "        inp_dim: 嵌入层的输入维度。\n",
    "        emb_dim: 嵌入层的输出维度。\n",
    "        hid_dim: CNN 隐层向量维度。\n",
    "        num_layers: CNN 层数。\n",
    "        kerner_size: 卷积核大小。\n",
    "    \"\"\"\n",
    "    def __init__(self, inp_dim, emb_dim, hid_dim, \n",
    "                 num_layers, kernel_size):\n",
    "        super().__init__()\n",
    "                \n",
    "        self.embed = nn.Embedding(inp_dim, emb_dim)\n",
    "        \n",
    "        self.emb2hid = nn.Linear(emb_dim, int(hid_dim/2))\n",
    "        self.hid2emb = nn.Linear(int(hid_dim/2), emb_dim)\n",
    "        \n",
    "        self.convs = nn.ModuleList([nn.Conv1d(in_channels = emb_dim, \n",
    "                                              out_channels = hid_dim, \n",
    "                                              kernel_size = kernel_size, \n",
    "                                              padding = (kernel_size - 1) // 2)\n",
    "                                    for _ in range(num_layers)])\n",
    "                \n",
    "    def forward(self, inputs):\n",
    "        # inputs.shape(): (src_len, inp_dim)\n",
    "        # conv_inp.shape(): (src_len, emb_dim)        \n",
    "        conv_inp = self.embed(inputs).permute(1, 0)\n",
    "        \n",
    "        for _, conv in enumerate(self.convs):\n",
    "            # 进行卷积运算\n",
    "            # conv_out.shape(): (src_len, hid_dim)\n",
    "            conv_out = conv(conv_inp)\n",
    "\n",
    "            # 经过激活函数\n",
    "            conved = F.glu(conv_out, dim=0)\n",
    "            \n",
    "            # 残差连接运算\n",
    "            conved = self.hid2emb(conved.permute(1, 0)).permute(1, 0)\n",
    "            conved = conved + conv_inp\n",
    "            conv_inp = conved\n",
    "        \n",
    "        # 卷积输出与词嵌入 element-wise 点加进行注意力运算\n",
    "        # combined.shape(): (src_len, emb_dim)\n",
    "        combined = conved + conv_inp\n",
    "        \n",
    "        return conved, combined"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CNNDecoder(nn.Module):\n",
    "    r\"\"\"Info: 序列到序列 CNN 解码器。\n",
    "\n",
    "    Args:\n",
    "        out_dim: 嵌入层的输入维度。\n",
    "        emb_dim: 嵌入层的输出维度。\n",
    "        hid_dim: CNN 隐层向量维度。\n",
    "        num_layers: CNN 层数。\n",
    "        kernel_size: 卷积核大小。\n",
    "    \"\"\"\n",
    "    def __init__(self, out_dim, emb_dim, hid_dim, \n",
    "                 num_layers, kernel_size, trg_pad_idx):\n",
    "        super().__init__()\n",
    "        \n",
    "        self.kernel_size = kernel_size\n",
    "        self.trg_pad_idx = trg_pad_idx\n",
    "        \n",
    "        self.embed = nn.Embedding(out_dim, emb_dim)\n",
    "        \n",
    "        self.emb2hid = nn.Linear(emb_dim, int(hid_dim/2))\n",
    "        self.hid2emb = nn.Linear(int(hid_dim/2), emb_dim)\n",
    "\n",
    "        self.attn_hid2emb = nn.Linear(int(hid_dim/2), emb_dim)\n",
    "        self.attn_emb2hid = nn.Linear(emb_dim, int(hid_dim/2))\n",
    "        \n",
    "        self.fc_out = nn.Linear(emb_dim, out_dim)\n",
    "        \n",
    "        self.convs = nn.ModuleList([nn.Conv1d(in_channels = emb_dim, \n",
    "                                              out_channels = hid_dim, \n",
    "                                              kernel_size = kernel_size)\n",
    "                                    for _ in range(num_layers)])\n",
    "        \n",
    "      \n",
    "    def calculate_attention(self, embed, conved, encoder_conved, encoder_combined):\n",
    "        # embed.shape(): (trg_len, emb_dim)\n",
    "        # conved.shape(): (hid_dim, trg_len)\n",
    "        # encoder_conved.shape(), encoder_combined.shape(): (src_len, emb_dim)\n",
    "        # 进行注意力层第一次线性运算调整维度\n",
    "        conved_emb = self.attn_hid2emb(conved.permute(1, 0)).permute(1, 0)\n",
    "        \n",
    "        # conved_emb.shape(): (trg_len, emb_dim])\n",
    "        combined = conved_emb + embed\n",
    "        # print(combined.size(), encoder_conved.size())\n",
    "        energy = torch.matmul(combined.permute(1, 0), encoder_conved)\n",
    "        \n",
    "        # attention.shape(): (trg_len, emb_dim])\n",
    "        attention = F.softmax(energy, dim=1)\n",
    "        attended_encoding = torch.matmul(attention, encoder_combined.permute(1, 0))\n",
    "        \n",
    "        # attended_encoding.shape(): (trg_len, emd_dim)\n",
    "        # 进行注意力层第二次线性运算调整维度\n",
    "        attended_encoding = self.attn_emb2hid(attended_encoding)\n",
    "        \n",
    "        # attended_encoding.shape(): (trg_len, hid_dim)\n",
    "        # 残差计算\n",
    "        attended_combined = conved + attended_encoding.permute(1, 0)\n",
    "                \n",
    "        return attention, attended_combined\n",
    "\n",
    "\n",
    "    def forward(self, targets, encoder_conved, encoder_combined):\n",
    "        # targets.shape(): (trg_len, out_dim)\n",
    "        # encoder_conved.shape(): (src_len, emb_dim)\n",
    "        # encoder_combined.shape(): (src_len, emb_dim)\n",
    "        conv_inp = self.embed(targets).permute(1, 0)\n",
    "        \n",
    "        src_len = conv_inp.shape[0]\n",
    "        hid_dim = conv_inp.shape[1]\n",
    "        \n",
    "        for _, conv in enumerate(self.convs):\n",
    "            #need to pad so decoder can't \"cheat\"\n",
    "            padding = torch.zeros(src_len,\n",
    "                                  self.kernel_size - 1).fill_(self.trg_pad_idx)\n",
    "            padded_conv_input = torch.cat((padding, conv_inp), dim=-1)\n",
    "        \n",
    "            #padded_conv_input = [batch size, hid dim, trg len + kernel size - 1]\n",
    "        \n",
    "            # 经过卷积运算\n",
    "            conved = conv(padded_conv_input)\n",
    "            \n",
    "            # 经过激活函数\n",
    "            conved = F.glu(conved, dim=0)\n",
    "            \n",
    "            # 注意力分数计算\n",
    "            attention, conved = self.calculate_attention(conv_inp, conved, \n",
    "                                                         encoder_conved, \n",
    "                                                         encoder_combined)\n",
    "                        \n",
    "            # 残差连接计算\n",
    "            conved = self.hid2emb(conved.permute(1, 0)).permute(1, 0)\n",
    "            \n",
    "            conved = conved + conv_inp         \n",
    "            conv_inp = conved\n",
    "\n",
    "        output = self.fc_out(conved.permute(1, 0))            \n",
    "        return output, attention"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "class EncoderDecoder(nn.Module):\n",
    "    r\"\"\"Info: 序列到序列 CNN 模型。\n",
    "    \"\"\"\n",
    "    def __init__(self, encoder, decoder):\n",
    "        super().__init__()\n",
    "        self.encoder = encoder\n",
    "        self.decoder = decoder\n",
    "        \n",
    "    def forward(self, enc_inp, dec_inp):\n",
    "        # 编码器，将源句子编码为向量输入解码器进行解码。\n",
    "        encoder_conved, encoder_combined = self.encoder(enc_inp)\n",
    "        \n",
    "        # 解码器，根据编码器隐藏状态和解码器输入预测下一个单词的概率\n",
    "        # 注意力层，源句子和目标句子之间进行注意力运算从而对齐\n",
    "        output, attention = self.decoder(dec_inp, encoder_conved, encoder_combined)\n",
    "        \n",
    "        return output, attention"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 构建一个基于CNN的序列到序列模型\n",
    "inp_dim, out_dim, emb_dim, hid_dim, num_layers, kernel_size = 4, 4, 8, 16, 1, 3\n",
    "encoder = CNNEncoder(inp_dim, emb_dim, hid_dim, num_layers, kernel_size)\n",
    "decoder = CNNDecoder(out_dim, emb_dim, hid_dim, num_layers, kernel_size, trg_pad_idx=0)\n",
    "model = EncoderDecoder(encoder, decoder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([4, 4])\n"
     ]
    }
   ],
   "source": [
    "enc_inp_seq = \"I love you !\"\n",
    "dec_inp_seq = \"我 爱 你 ！\"\n",
    "enc_inp, dec_inp = [], []\n",
    "\n",
    "# 自己构造的的词典\n",
    "voc_1 = {\"I\": 1, \n",
    "        \"love\": 2,\n",
    "        \"you\": 3,\n",
    "        \"!\": 0}\n",
    "voc_2 = {\"我\": 1,\n",
    "        \"爱\": 2,\n",
    "        \"你\": 3,\n",
    "        \"！\": 0}\n",
    "\n",
    "for word in enc_inp_seq.split():\n",
    "    enc_inp.append(voc_1[word])\n",
    "\n",
    "enc_inp = torch.tensor(enc_inp)\n",
    "\n",
    "for word in dec_inp_seq.split():\n",
    "    dec_inp.append(voc_2[word])\n",
    "    \n",
    "dec_inp = torch.tensor(dec_inp)\n",
    "output, _ = model(enc_inp, dec_inp)\n",
    "\n",
    "# output (dec_seq_len, dec_voc_size)\n",
    "print(output.size())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 习题26.5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;写出6层编码器和6层解码器组成的Transformer的所有参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答思路：**  \n",
    "\n",
    "1. 给出Transformer模型的描述\n",
    "2. 分析Transformer模型结构\n",
    "3. 写出6层编码器和6层解码器组成的Transformer的所有参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答步骤：**   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第1步：Transformer模型的描述**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;根据书中第26章的本章概要的Transformer模型的描述：\n",
    "\n",
    "> Transformer是完全基于注意力机制的序列到序列学习模型。使用注意力实现编码、解码及编码器和解码器之间的信息传递。  \n",
    "> Transformer拥有非常简单的结构。编码器的输入是输入单词序列，编码器的输入层是\n",
    "> $$ H_E^{(0)} = E_E + P_E $$\n",
    "> 编码器的第$l$个编码层由多头自注意力子层和前馈网络子层组成：\n",
    "> $$ Z_E^{(l)} = \\text{norm} (H_E^{(l - 1)} + \\text{multi\\_head}(H_E^{(l - 1)}, H_E^{(l - 1)}, H_E^{(l - 1)})) \\\\\n",
    "H_E^{(l)} = \\text{norm} (Z_E^{(l)} + \\text{forward}(Z_E^{(l)})) $$\n",
    "> 解码器的输入是已生成的输出单词序列，解码器的输入层是\n",
    "> $$ H_D^{(0)} = E_D + P_D $$\n",
    "> 解码器的第$l$个解码层由多头自注意力子层、多头注意力子层、前馈网络子层组成：\n",
    "> $$ I_D^{(l)} = \\text{norm} (H_D^{(l - 1)} + \\text{multi\\_head} (H_D^{l - 1}, H_D^{l - 1}, H_D^{l - 1})) \\\\\n",
    "Z_D^{(l)} = \\text{norm} (I_D^{(l)} + \\text{multi\\_head}(I_D^{(l)}, H_E^{(l)}, H_E^{(l)})) \\\\\n",
    "H_D^{(l)} = \\text{norm} (Z_D^{(l)} + \\text{forward}(Z_D^{l})) $$\n",
    "> 解码器的输出层计算下一个位置单词出现的条件概率。\n",
    "> $$ p_i = \\text{softmask}(W_O \\cdot h_i^{(L)}) $$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第2步：分析Transformer模型结构**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![习题26.5解答图Transformer.png](../images/26-5-Transformer.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;上图来自论文[《Attention Is All You Need》](https://arxiv.org/abs/1706.03762)中的Transformer模型架构图，根据该图，逐一分析各个模块包含的参数："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 输入Embedding层"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;Embedding层分为连接编码器的输入 Embedding 层和连接解码器的输出 Embedding 层，包含各自的 Word Embedding 参数，假设输入 Embedding 和输出 Embedding 的参数规模大小相同，经过独热（one-hot）编码之后，某时刻输入至编码器和解码器对应 Embedding 的向量分别为 ${\\boldsymbol{\\tilde X}_i} \\in {\\mathbb R}^{N_i \\times L_i}, {\\boldsymbol{\\tilde X}_{o}} \\in {\\mathbb R}^{N_o \\times L_o}$，$N_E, L_E$ 和 $N_D, L_D$ 分别为编码器和解码器输入的向量和序列长度，经过 Word Embedding 编码得到输入给编码器和解码器的向量 $\\boldsymbol X_{i}$ 和 $\\boldsymbol X_{o}$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\boldsymbol{X}_i = \\boldsymbol{W}_i \\boldsymbol{\\tilde X}_i \\\\\n",
    "\\boldsymbol{X}_o = \\boldsymbol{W}_o \\boldsymbol{\\tilde X}_o\n",
    "$$\n",
    "其中 $\\boldsymbol{W}_i \\in \\mathbb{R}^{M \\times N_E}, \\boldsymbol{W}_o \\in {\\mathbb R}^{M \\times N_D}$，$M$ 是词向量编码后的维度。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 编码器和解码器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;结构上，Transformer 编码器和解码器的唯一区别在于解码器的注意力层会存在一个 Attention Mask 的掩码矩阵，但在参数结构规模上是完全一样的，因此只需要分析编码器的参数，同理也就知道解码器上的参数了，用参数下标 $E$ 和 $D$ 分别区分编码器和解码器的参数；另外单独每一层的编码器或解码器参数结构也是一样的，所以接下来分析单层编码器的参数即可。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;首先，将编码器对应 Embedding 的输出记作 ${\\boldsymbol X}_{E}^{0}$，表示最底层至向上第一层编码器的输入，由于每一层编码器都是前馈的方式连接，第 $l-1$ 层的输出是第 $l$ 层的输入，对于第 $l$ 层的编码器， 时刻 $t$ 的输入向量 ${\\boldsymbol X}_{E}^{l-1}$ 首先进入注意力层，如下图所示："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![26-5-Attention.png](../images/26-5-Attention.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;由于多头注意力中的每个头的计算原理都一样，这里只分析单头注意力，第 $i$ 个头的注意力经过三次线性运算得到查询矩阵$Q$，键矩阵$K$和值矩阵$V$，如下：\n",
    "\n",
    "$$\n",
    "\\boldsymbol{Q}_i^{(l)} = \\boldsymbol{W}^{(l)}_{i,q} \\boldsymbol{X}_{i}^{(l-1)} + \\boldsymbol{b}^{(l)}_{i,q} \\\\\n",
    "\\boldsymbol{K}_i^{(l)} = \\boldsymbol{W}^{(l)}_{i,k} \\boldsymbol{X}_{i}^{(l-1)} + \\boldsymbol{b}^{(l)}_{i,k} \\\\ \n",
    "\\boldsymbol{V}_i^{(l)} = \\boldsymbol{W}^{(l)}_{i,v} \\boldsymbol{X}_{i}^{(l-1)} + \\boldsymbol{b}^{(l)}_{i,v}\n",
    "$$\n",
    "\n",
    "这里需要更新的参数有三组权重 $\\boldsymbol{W}^{(l)}_{i,q}, \\boldsymbol{W}^{(l)}_{i,k}, \\boldsymbol{W}^{(l)}_{i,v} \\in \\mathbb{R}^{ M \\times (M / h)}$，其中 $h$ 为head的数量；偏置 $\\boldsymbol{b}^{(l)}_{i,q}, \\boldsymbol{b}^{(l)}_{i,k}, \\boldsymbol{b}^{(l)}_{i,v} \\in {\\mathbb R}^{M}$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;再进行自注意力运算，这里不涉及到可更新参数，可直接跳过，得到自注意力层的输出 $\\boldsymbol{Y}_i^{(l)}$，再经过一次线性层运算，可得\n",
    "\n",
    "$$\n",
    "\\boldsymbol{H}^{(l)}_i = \\boldsymbol{W}_{i,h}^{(l)} \\boldsymbol{Y}_{i}^{(l)} + \\boldsymbol{b}^{(l)}_h\n",
    "$$\n",
    "\n",
    "又有一组需要更新的权重和偏差 $\\boldsymbol{W}_h^{(l)} \\in \\mathbb{R}^{M \\times M}, \\boldsymbol{b}_h^{(l)} \\in \\mathbb{R}^M$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;再经过残差运算和层归一化，涉及到少量层归一化的参数；把归一化后的输出记作 $\\boldsymbol{Z}^{(l)}_i$，这便是注意力模块的输出，\n",
    "\n",
    "$$\n",
    "\\boldsymbol{Z}_i^{(l)} = \\text{LayerNorm}(\\boldsymbol{H}_i^{(l)}) + \\boldsymbol{X}_i^{(l-1)}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;然后输入至前馈神经网络层，经过两次线性运算和残差连接以及层归一化，可得\n",
    "\n",
    "$$\n",
    "\\boldsymbol{S}_{i,1}^{(l)} = \\text{ReLU} \\left( \\boldsymbol{W}_{i,1}^{(l)} \\boldsymbol{Z}_i^{(l)} + \\boldsymbol{b}_{i,1}^{(l)} \\right) \\\\\n",
    "\\boldsymbol{S}_{i,2}^{(l)} = \\boldsymbol{W}_{i,2}^l \\boldsymbol{S}_{i,1}^{(l)} + \\boldsymbol{b}_{i,2}^{(l)} \\\\\n",
    "\\boldsymbol{X}_i^{(l)} = \\text{LayerNorm} \\left( \\boldsymbol{S}_{i,2}^{(l)} \\right) + \\boldsymbol{Z}_i^{(l)}\n",
    "$$\n",
    "\n",
    "这里需要更新的参数有两层前馈神经网络的权重和偏差 $\\boldsymbol{W}_{i,1}^{(l)} \\in \\mathbb{R}^{ M \\times D}, \\boldsymbol{W}_{i,2}^{(l)} \\in \\mathbb{R}^{D \\times M}, \\boldsymbol{b}_{i,1}^{(l)} \\in \\mathbb{R}^D, \\boldsymbol{b}_{i,2}^{(l)} \\in \\mathbb{R}^M$，以及层归一化中的一些参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 输出层"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;对于6层编码器解码器的Transformer，输出层的输入为最后一层解码器的输出 $\\boldsymbol{X}_{D}^{6}$，经过一次线性运算\n",
    "\n",
    "$$\n",
    "\\boldsymbol{\\tilde Y}_{D} = \\boldsymbol{W}_{\\tilde Y} \\boldsymbol{X}_{D}^{6} + \\boldsymbol{b}_{\\tilde Y} \n",
    "$$\n",
    "\n",
    "便可得到最终输出结果。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;需要更新参数有输出层的权重和偏差$\\boldsymbol{W}_{\\tilde Y} \\in \\mathbb{R}^{ M \\times N_{\\tilde Y}}, \\boldsymbol{b}_{\\tilde Y} \\in \\mathbb{R}^{N_{\\tilde Y}} $，其中$N_{\\tilde Y}$ 为输出向量的维度。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**第3步：写出6层编码器和6层解码器组成的Transformer的所有参数**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&emsp;&emsp;6层编码器和6层解码器组成的Transformer的所有参数包含以下三部分："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. Embedding 层，分别为编码器和解码器对应的 Word Embedding 参数 $\\boldsymbol{W}_{i}, \\boldsymbol{W}_{o}$\n",
    "\n",
    "2. 编码器的参数，对于第 $l$ 层编码器，注意力层有$\\boldsymbol{W}^{(l)}_{E,q}, \\boldsymbol{W}^{(l)}_{E,k}, \\boldsymbol{W}^{(l)}_{E,v}, \\boldsymbol{W}^{(l)}_{E,h}, \\boldsymbol{b}^{(l)}_{E,q}, \\boldsymbol{b}^{(l)}_{E,k}, \\boldsymbol{b}^{(l)}_{E,v}, \\boldsymbol{b}_{i,h}^{(l)}$，前馈网络层有 $\\boldsymbol{W}_{i,1}^{(l)}, \\boldsymbol{W}_{i,2}^{(l)}, \\boldsymbol{b}_{E,1}^{(l)}, \\boldsymbol{b}_{i,2}^{(l)}$，其中 $l = {1,2,3,4,5,6}$，每一层都有一些层归一化的隐藏参数\n",
    "\n",
    "3. 同理，解码器的参数，对于第 $l$ 层解码器，注意力层有$\\boldsymbol{W}^{(l)}_{D,q}, \\boldsymbol{W}^{(l)}_{D,k}, \\boldsymbol{W}^{(l)}_{D,v}, \\boldsymbol{W}_{D,h}^{(l)}, \\boldsymbol{b}^{(l)}_{D,q}, \\boldsymbol{b}^{(l)}_{D,k}, \\boldsymbol{b}^{(l)}_{D,v}, \\boldsymbol{b}_{D,h}^{(l)}$，前馈网络层有 $\\boldsymbol{W}_{D,1}^{(l)}, \\boldsymbol{W}_{D,2}^{(l)}, \\boldsymbol{b}_{D,1}^{(l)}, \\boldsymbol{b}_{D,2}^{(l)}$，其中 $l = {1,2,3,4,5,6}$，每一层都有一些层归一化的隐藏参数\n",
    "\n",
    "4. 输出层，包含一组参数 $\\boldsymbol{W}_{\\tilde Y} ,\\boldsymbol{b}_{\\tilde Y} $"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 参考文献"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "【1】Jonas Gehring and Michael Auli and David Grangier and Denis Yarats and Yann N. Dauphin. 2017. Convolutional Sequence to Sequence Learning[J]. arXiv, abs/1705.03122  \n",
    "【2】Ashish Vaswani and Noam Shazeer and Niki Parmar and Jakob Uszkoreit and Llion Jones and Aidan N. Gomez and Lukasz Kaiser and Illia Polosukhin. 2017. Attention Is All You Need[J]. arXiv, abs/1706.03762"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.5"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "273.188px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  },
  "vscode": {
   "interpreter": {
    "hash": "eb16a45d3969af144375f8a71e40c490388b4fb3c27d50b5e869e018dcf9dd9d"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
